package io.kiki.sba.registry.server.clientInterface.scheduler.timertask;


import io.kiki.sba.registry.common.model.DataUtils;
import io.kiki.sba.registry.common.model.InterestGroup;
import io.kiki.sba.registry.common.model.Tuple;
import io.kiki.sba.registry.common.model.store.Publisher;
import io.kiki.sba.registry.common.model.store.Subscriber;
import io.kiki.sba.registry.common.model.store.Watcher;
import io.kiki.sba.registry.server.clientInterface.bootstrap.Config;
import io.kiki.sba.registry.server.clientInterface.store.Publishers;
import io.kiki.sba.registry.server.clientInterface.store.Subscribers;
import io.kiki.sba.registry.server.clientInterface.store.Watchers;
import io.kiki.sba.registry.util.ConcurrentUtils;
import io.kiki.sba.registry.util.NamedThreadFactory;
import io.prometheus.client.Gauge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class CacheCountTask {
    private static final Logger logger = LoggerFactory.getLogger(CacheCountTask.class);

    @Autowired
    Publishers sessionPublishers;

    @Autowired
    Subscribers sessionSubscribers;

    @Autowired
    Watchers sessionWatchers;

    @Autowired
    Config config;

    static Tuple<List<Subscriber>, List<Subscriber>> splitMultiSub(List<Subscriber> subs) {
        if (CollectionUtils.isEmpty(subs)) {
            return new Tuple<>(Collections.emptyList(), Collections.emptyList());
        }

        int initSize = subs.size() / 2;
        List<Subscriber> notMulti = new ArrayList<>(initSize);
        List<Subscriber> multi = new ArrayList<>(initSize);

        for (Subscriber sub : subs) {
            if (sub.isAcceptMulti()) {
                multi.add(sub);
            } else {
                notMulti.add(sub);
            }
        }
        return new Tuple<>(notMulti, multi);
    }

    private static void printInstanceIdGroupAppCount(String prefix, Map<String, Map<String, Map<String, Tuple<Integer, Integer>>>> counts) {
        for (Entry<String, Map<String, Map<String, Tuple<Integer, Integer>>>> count : counts.entrySet()) {
            final String instanceId = count.getKey();
            for (Entry<String, Map<String, Tuple<Integer, Integer>>> groupCounts : count.getValue().entrySet()) {
                final String group = groupCounts.getKey();
                for (Entry<String, Tuple<Integer, Integer>> apps : groupCounts.getValue().entrySet()) {
                    final String app = apps.getKey();
                    Tuple<Integer, Integer> tupleCount = apps.getValue();
                    logger.info("{}{},{},{},{},{}", prefix, instanceId, group, app, tupleCount.o1, tupleCount.o2);
                }
                ConcurrentUtils.sleepUninterruptibly(2, TimeUnit.MILLISECONDS);
            }
        }
    }

    private static void printInstanceIdGroupCount(String prefix, Map<String, Map<String, Tuple<Integer, Integer>>> counts, Gauge gauge, Gauge dataIDGauge) {
        gauge.clear();
        dataIDGauge.clear();
        for (Entry<String, Map<String, Tuple<Integer, Integer>>> count : counts.entrySet()) {
            final String instanceId = count.getKey();
            for (Entry<String, Tuple<Integer, Integer>> groups : count.getValue().entrySet()) {
                final String group = groups.getKey();
                Tuple<Integer, Integer> tupleCount = groups.getValue();
                gauge.labels(instanceId, InterestGroup.normalizeGroup(group)).set(tupleCount.o1);
                dataIDGauge.labels(instanceId, InterestGroup.normalizeGroup(group)).set(tupleCount.o2);
                logger.info("{}{},{},{},{}", prefix, instanceId, group, tupleCount.o1, tupleCount.o2);
            }
        }
    }

    @PostConstruct
    public boolean init() {
        final int intervalSec = config.getCacheCountIntervalSecs();
        if (intervalSec <= 0) {
            logger.info("cache count off with intervalSecs={}", intervalSec);
            return false;
        }
        ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("CacheCountTask"));
        executor.scheduleAtFixedRate(this::syncCount, intervalSec, intervalSec, TimeUnit.SECONDS);
        return true;
    }

    boolean syncCount() {
        try {
            List<Publisher> pubs = sessionPublishers.getDataList();
            List<Subscriber> subs = sessionSubscribers.getDataList();
            List<Watcher> wats = sessionWatchers.getDataList();

            Map<String, Map<String, Tuple<Integer, Integer>>> pubGroupCounts = DataUtils.countGroupByInstanceIdGroup(pubs);
            printInstanceIdGroupCount("[PubGroup]", pubGroupCounts, Metrics.PUB_GAUGE, Metrics.PUB_DATA_ID_GAUGE);

            Tuple<List<Subscriber>, List<Subscriber>> multiSubs = splitMultiSub(subs);

            Map<String, Map<String, Tuple<Integer, Integer>>> notMultiSubGroupCounts = DataUtils.countGroupByInstanceIdGroup(multiSubs.o1);
            Map<String, Map<String, Tuple<Integer, Integer>>> multiSubGroupCounts = DataUtils.countGroupByInstanceIdGroup(multiSubs.o2);
            printInstanceIdGroupCount("[NotMultiSubGroup]", notMultiSubGroupCounts, Metrics.NOT_MULTI_SUB_GAUGE, Metrics.NOT_MULTI_SUB_DATA_ID_GAUGE);
            printInstanceIdGroupCount("[MultiSubGroup]", multiSubGroupCounts, Metrics.MULTI_SUB_GAUGE, Metrics.MULTI_SUB_DATA_ID_GAUGE);

            Map<String, Map<String, Tuple<Integer, Integer>>> watGroupCounts = DataUtils.countGroupByInstanceIdGroup(wats);
            printInstanceIdGroupCount("[WatGroup]", watGroupCounts, Metrics.WAT_COUNTER, Metrics.WAT_DATA_ID_COUNTER);

            Map<String, Map<String, Map<String, Tuple<Integer, Integer>>>> pubCounts = DataUtils.countGroupByInstanceIdGroupApp(pubs);
            printInstanceIdGroupAppCount("[Pub]", pubCounts);

            Map<String, Map<String, Map<String, Tuple<Integer, Integer>>>> subCounts = DataUtils.countGroupByInstanceIdGroupApp(subs);
            printInstanceIdGroupAppCount("[Sub]", subCounts);

            Map<String, Map<String, Map<String, Tuple<Integer, Integer>>>> watCounts = DataUtils.countGroupByInstanceIdGroupApp(wats);
            printInstanceIdGroupAppCount("[Wat]", watCounts);
            return true;
        } catch (Throwable e) {
            logger.error("cache count error", e);
            return false;
        }
    }
}
